<?php
declare (strict_types=1);

namespace app\service\websocket\privater;

use Swoole\Exception;
use Swoole\Server;
use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebsocketServer;
use think\Config;
use think\Container;
use think\Request;
use think\swoole\contract\websocket\HandlerInterface;
use think\swoole\Table;
use think\swoole\Websocket;

class Handler implements HandlerInterface
{

    protected $key;
    protected $alias;
    /** @var WebsocketServer */
    protected $server = null;
    /** @var Websocket */
    protected $websocket = null;
    /** @var Table */
    protected $table = null;
    /** @var Table */
    protected $u2fd = null;
    /** @var Table */
    protected $fd2u = null;


    public function __construct(Server $server, Config $config, Container $container, Websocket $websocket)
    {
        $this->server = $server;
        $this->websocket = $websocket;
        $this->key = $config->get('websocket.key');
        $this->alias = $config->get('websocket.alias');
        $this->table = $container->get(Table::class);
        $this->u2fd = $this->table->u2fd;
        $this->fd2u = $this->table->fd2u;
    }

    /**
     * "onOpen" listener.
     *
     * @param int $fd
     * @param Request $request
     */
    public function onOpen($fd, Request $request)
    {
        //处理鉴权
        $unique = $request->param('token');
        if ($unique == 0)
            $this->websocket->close($this->websocket->getSender());

        //已连接替换下旧连接socket
        if ($this->table->u2fd->exist((string)$unique)) {
            $old = $this->table->u2fd->get((string)$unique, 'fd');
            $this->close($old);
            $this->table->fd2u->del((string)$old);
        }
        $this->table->u2fd->set((string)$unique, ['fd' => $this->websocket->getSender()]);
        $this->table->fd2u->set((string)$this->websocket->getSender(), ['unique' => $unique]);
        foreach ($this->table->u2fd as $row) {
            $this->pack(['msg' => 'online', 'unique' => $unique, 'online_count' => $this->u2fd->count(), 'fd' => $this->websocket->getSender()]);
        }
        return true;
    }

    /**
     * "onMessage" listener.
     *  only triggered when event handler not found/返回false触发监听,返回true直接完成
     *
     * @param Frame $frame
     * @return bool
     */
    public function onMessage(Frame $frame)
    {
        try {
            $obtain = json_decode($frame->data, true);
            if (!is_array($obtain))
                $this->close();
            if (isset($obtain[$this->key])) {
                if (false === strpos($obtain[$this->key], '.')) {
                    $callback = $this->callAllFunc($obtain[$this->key], $obtain, $this->table->fd2u->get((string)$this->websocket->getSender(), 'unique'));
                } else {
                    $cmds = explode('.', $obtain[$this->key]);
                    $alias = array_shift($cmds);
                    $callback = [];
                    foreach ($cmds as $cmd) {
                        if (in_array($cmd, $this->alias[$alias]['methods'], true)) {
                            $back = $this->callOnceFunc($alias, $cmd, $obtain, $this->table->fd2u->get((string)$this->websocket->getSender(), 'unique'));
                            if ($back != false) {
                                $callback[$alias . '.' . $cmd] = $back;
                            }
                        }
                    }
                }
                $this->pack($callback);
            } else {
                $this->close();
            }
        } catch (Exception $exception) {
            $this->close();
        }
        return true;
    }

    protected function callOnceFunc($alias, $method, $data, $unique)
    {
        $class = $this->alias[$alias]['class'];
        return $class::$method($data, $unique, $this->server, $this->websocket->getSender());
    }

    protected function callAllFunc($alias, $data, $unique)
    {
        $class = $this->alias[$alias]['class'];
        $callback = [];
        foreach ($this->alias[$alias]['methods'] as $method) {
            $back = $class::$method($data, $unique, $this->server, $this->websocket->getSender());
            if ($back != false) {
                $callback[$alias . '.' . $method] = $back;
            }
        }
        return $callback;
    }

    /**
     * 下线用户
     * @param int $fd
     */
    protected function close(int $fd)
    {
        $this->websocket->close($fd);
    }

    /**
     * "onClose" listener.
     *
     * @param int $fd
     * @param int $reactorId
     */
    public function onClose($fd, $reactorId)
    {
        $unique = $this->table->fd2u->get((string)$this->websocket->getSender(), 'unique');
        $this->table->u2fd->del((string)$unique);
        $this->table->fd2u->del((string)$this->websocket->getSender());
        foreach ($this->table->u2fd as $row) {
            $this->pack(['msg' => 'offline', 'unique' => $unique, 'online_count' => $this->u2fd->count(), 'fd' => $this->websocket->getSender()]);
        }
        return true;
    }

    protected function pack($data)
    {
        if (count($data) > 0) {
            $return = [
                'callback' => $data,
                'pack_timestamp' => time()
            ];
            $this->server->push($this->websocket->getSender(), json_encode($return, 320));
        }
    }
}
